- 線上 Kotlin 練習編譯器:https://try.kotlinlang.org/
- 教學內容會融入先前章節提過的知識,若遇到問題可以從前面的章節依序閱讀
今天快速進入課程主題 Class (類別),在 Kotlin 中類別的宣告如下:
上圖程式架構中,Customer()
是一個最純粹的類別。若想在建立類別的同時,強制指定必須要傳入參數進行初始,否則拒絕建立的時候,就以 CustomerA
方式呈現,其實這個機制稱為建構子,完整的寫法在 CustomerB 展示,A 與 B 的寫法呈現的結果是完全相同的,所以一般在程式設計時可以省略 constructor (建構子) 關鍵字。
你一定想問,為什麼還要有 constructor
呢?那是因為省略這個關鍵字的前提是:不指定任何的可見性修飾詞 (private, protected, internal, public)
,也不需要使用程式註釋,若是有使用到其中一個,就需要加上 construcotr
關鍵字,如 CustomerC()
。
上段提到的程式註釋,這裡舉其中一種用法 @Deprecated
說明,此註釋主要是將舊的程式碼註記棄用,改建議使用新的寫法,在 IDE 使用到時,就會如下圖所示出現提示訊息,有興趣了解更多註釋用法可以搜尋關鍵字:Kotlin annotations。
當宣告好了類別需要使用時,就以程式碼 var 物件名稱 = 類別名稱()
的方式使用,與其它程式語言比較,省略了 new
關鍵字,下圖的 Line: 14, 15 行建立了兩個物件,另外,這邊可以發現兩種類別宣告方式,CustomerA
在建構子中加入了 val、var
,此時就會自動在類別內生成兩個屬性,屬性的意思稍微後面一點說明。
CustomerB
則是在建構子註明了接受哪些參數,另外的在類別內自訂兩個屬性 (Line: 9, 10),這邊需注意的是就算你的建構子參數與類別屬性為相同名稱,也是視為不同的,所以並不會衝突,但是不利於程式易讀性,因此建議在建構子參數中以底線為開頭,詳細示範在之後的段落介紹。
下圖的示範重點是,當宣告一個新的 CustomerB
實體,初始設定的 _account
透過建構子方式指定時,可以在類別內設計通通轉換為小寫儲存在屬性 account
上。
接著示範一個無建構子的類別,純粹的宣告一個類別內含四個屬性存放資料,屬性全部都有指定預設值,如此一來在建立物件時才不會發生錯誤。在 Line: 16, 17 試著修改一個 val
屬性時,系統會發出錯誤,你可以把 val
當作是唯獨,除了初始值之外,就不得再修改。
屬性的取用方式是以 物件名稱.屬性名稱
,在 Line: 19, 20 展示。特別一提,若在程式中使用 LocalDate
時間型態的話,要在最上方區段加入 import java.time.LocalDate
。
下圖中的建構子參數名稱命名方式採用剛剛提到的底線開頭,主要用於識別參數與屬性,另外在 main
區塊,展示了建立物件時,除了使用建構子參數外,也能使用 object initializers
(物件初始設定式),加上 .apply() { … }
,可省略如上圖 Line: 14, 15 每次賦予值時都要加上物件名稱。
可能會好奇屬性宣告跟建構子參數宣告在實務上的差別,下圖示範的是將屬性提到建構子參數宣告,可以很清楚的發現,無論在宣告類別或建立物件時,你將會得到一整段又長又扁平的程式碼,當你的類別一多,對於程式可維護性和易用性來說,並不是一件好事。
不過作者也有看到過下圖的呈現方式,將建構子每個參數用換行分隔,提供給各位參考:
類別的構成除了建構子外,還有 init
(初始化) 及 second constructor
(次建構子) 可以使用,初始化表示物件建立時,一定會優先執行到的部分;次建構子則可提供彈性的設計。
下圖的編號清楚的編排出程式執行順序,各位可從 fun main
的紅色 1 開始追蹤,接續到 class Customer
(紅 2),建立物件時,首先會執行到 init
(紅 3) 內的程式,因此在右邊結果可看到灰色 3,接續到屬性的宣告,這邊有使用 also(::println)
協助列出結果,在正常程式設計時不需這樣使用。
執行完 4 ~ 6 後,因為 custA
並無使用到建構子,到這邊算是完成了 custA
的物件建立。接著看藍色 7 custB
,執行步驟跟紅色 custA
差不多,不過這邊有特別指定了次建構子,在流程 8 透過 this
關鍵字將其中兩個參數傳至主建構子使用,等完成了跟 custA
一樣的流程時,會回到藍 14 繼續執行 15 ~ 16。
了解物件的內部執行流程,能避免錯誤順序邏輯導致的設計錯誤,你可試著幫自己的程式加入斷點,來看看整體流程是否符合預期。
在類別中使用屬性可以搭配 private
或 getter、setter
來靈活使用, private set
將 setter
變為私有,宣告在屬性的下方,account
此時限制在外部使用時,僅能讀取無法修改。
public var account: String = _account.toLowerCase()
private set
private var
直接將整個屬性變成私有,外部建立物件時,完全無法讀取與修改這個屬性。
public var password: String = _password
透過 getter
可以在取值時,基於其它邏輯條件決定要回傳的值,以下範例情境為:為當月份壽星時,自動視為 VIP,若非當月壽星,則傳回一般 VIP 註記,這邊也將 _isVIP
變更為 private
,若要修改值,只能透過公開的方法來實作。
private var _isVIP: Boolean = false
public val isVIP: Boolean
get() {
if (birthday?.getMonth() == LocalDate.now().getMonth())
{
return true
}
return _isVIP;
}
這邊以更改密碼為例,需要在類別中建立一個公開的方法,再透過 this
關鍵字將類別內部隱藏的屬性更新。
fun changePassword(_newPassword: String) {
this.password = _newPassword;
}
完整範例可以參考下圖,可以看到第 24、25 的用法會造成程式發生錯誤,皆是因為 private
的關係,Line: 26 是正確用法。
最後,要講解的是繼承的概念,我們以使用者當作主體,再區分為一般顧客或員工,繼承主要是讓類別能夠共用部分設計架構之外,又能保有個體的特色,詳細概念在此不多述,我們關注在 Kotlin 的使用方式。要繼承一個類別使用冒號 :
達成 (Line: 19, 36),要被繼承的類別則要加上 open
關鍵字 (Line: 3),一旦繼承時,除了 private
屬性 (Line: 5) 或方法之外,其它都可以繼承到子類別上,所以即便在 Customer
中並沒有宣告 account
,但你仍可以透過 cust.account
取得帳號,因為繼承來自主類別 User
。
子類別無法存取到在繼承對象中被設定成 private
的內容,一樣要透過繼承的主類別 public 方法 changePassword
才能變更到 private password
屬性,以範例來說,可以透過 employee.changePassword()
方法來變更密碼屬性,縱使 class Employee
當中並沒有該方法,不過經由繼承 User 後就能夠使用。
繼承當中也有覆寫的概念,以 override
當作關鍵字 (Line: 31, 40),可以在子類別中自行設計同名的方法,在實作時也會直接以子類別的方法執行,若搭配 super
關鍵字 (Line: 41),則既可以往上層執行主類別的方法,接續還可以執行子類別的方法 (或顛倒順序)。
這個部分用文字形容會難以消化,請參照上圖示範,自行動手嘗試最終會印出什麼結果!今日的課程就到這邊,想知道更詳細的物件導向內容至下方閱讀參考資料,我們明天見!
資料參考
Classes and Inheritance - Kotlin Programming Language
https://kotlinlang.org/docs/reference/classes.htmlVisibility Modifiers - Kotlin Programming Language
https://kotlinlang.org/docs/reference/visibility-modifiers.htmlProperties and Fields - Kotlin Programming Language
https://kotlinlang.org/docs/reference/properties.html
今天回來看本篇文章發現內容的:類別/物件,有劃分不明確的部分,特別修正了一下,對之前的刊誤部分,若有造成概念上的認知混淆感到抱歉。